// Standart libraries headers
#include <iostream>

#include <boost/program_options.hpp>
#include <boost/filesystem/operations.hpp>

#include "../include/colors.hpp"
#include "../include/key_generator.hpp"
#include "../include/file_encryptor_decryptor.hpp"
#include "../include/json_reader.hpp"
#include "../include/json_writer.hpp"

enum class
{
  DEFAULT_KEY_LENGTH  = 4,
  KEY_LENGTH_MIN      = 2,
  KEY_LENGTH_MAX      = std::numeric_limits<uint8_t>::max()
};

void save_new_json_obj(alexcr4ckpentest::JsonWriter& j_writer) noexcept
{

}

int main(int argc, char** argv)
{
  namespace po = boost::program_options;
  namespace fs = boost::filesystem;

  uint8_t key_length{};
  std::string key_file_name{};
  po::variables_map parsed_options{};
  po::options_description available_options{"Available options"};

  available_options.add_options()
      ("help,h", "Show help")
      ("encrypt,e", po::value<std::string>(), "File to encrypt")
      ("decrypt,d", po::value<std::string>(), "File to decrypt")
      ("out-key-file,o", po::value<std::string>(), "Output key file (json)")
      ("in-key-file,i", po::value<std::string>(), "Input key file (json)")
      ("key-length,l", po::value<uint16_t>(&key_length)->default_value(DEFAULT_KEY_LENGTH), "Key length")
      ;

  try
  {
    po::store(po::parse_command_line(argc, argv, available_options), parsed_options);
    po::notify(parsed_options);
  }
  catch (const po::error& err)
  {
    std::cerr << alexcr4ckpentest::colors::red << "[-] " << alexcr4ckpentest::colors::none << "Error: " << err.what() << "\n";
    return 1;
  }

  if (parsed_options.size() < 2 || parsed_options.count("help"))
  {
    std::cout << available_options;
    return 0;
  }

  if (key_length < KEY_LENGTH_MIN || key_length > KEY_LENGTH_MAX)
  {
    std::cout << alexcr4ckpentest::colors::red << "[-] " << alexcr4ckpentest::colors::none << "Error: key length is short or long!\n";
    return 1;
  }

  if (parsed_options.count("out-key-file"))
  {
    key_file_name = parsed_options["out-key-file"].as<std::string>();
  }
  else if (parsed_options.count("in-key-file"))
  {
    key_file_name = parsed_options["in-key-file"].as<std::string>();
  }

  alexcr4ckpentest::JsonReader json_file_reader{};
  alexcr4ckpentest::JsonWriter json_file_writer{};
  json_file_reader.set_filename(key_file_name);
  json_file_writer.set_filename(key_file_name);

  if (!fs::exists(key_file_name))
  {
    json_file_writer.write(R"(
          {
              "encrypted": false,
              "filename": null,
              "base_key": null,
              "salt_key": null
          }
      )"_json);
  }

  alexcr4ckpentest::KeyGenerator key_pair_generator{};
  alexcr4ckpentest::FileEncryptorDecryptor file_encryptor_decryptor{};
  auto json_obj{json_file_reader.read()};

  if (parsed_options.count("encrypt"))
  {
    auto target_filename {fs::absolute(parsed_options["encrypt"].as<std::string>()).string()};

    if (!fs::exists(target_filename))
    {
      std::cerr << alexcr4ckpentest::colors::red << "[-] " << alexcr4ckpentest::colors::none << "Error: could not open file: " << target_filename << "\n";
      return 1;
    }

    if (!json_obj["filename"].is_null())
    {
      if (json_obj["filename"] != target_filename || json_obj["encrypted"])
      {
        std::cerr << alexcr4ckpentest::colors::red << "[-] " << alexcr4ckpentest::colors::none << "Error: different files encrypted or this file already encrypted!\n";
        return 1;
      }
    }

    const auto [base_key, salt_key] = key_pair_generator.generate(key_length);

    file_encryptor_decryptor.set_target_file(target_filename);
    file_encryptor_decryptor.process_data(base_key, salt_key, alexcr4ckpentest::FileEncryptorDecryptor::mode_t::encrypt);

    json_obj["encrypted"] = true;
    json_obj["filename"] = target_filename;
    json_obj["base_key"] = base_key;
    json_obj["salt_key"] = salt_key;
    json_file_writer.write(json_obj);

    std::cout << alexcr4ckpentest::colors::green << "[+] " << alexcr4ckpentest::colors::none << "Success: file has been encrypted: " << target_filename << "\n";
    std::cout << alexcr4ckpentest::colors::green << "[+] " << alexcr4ckpentest::colors::none << "Success: keys are saved in file: " << key_file_name << "\n";
  }
  else if (parsed_options.count("decrypt"))
  {
    auto target_filename {fs::absolute(parsed_options["decrypt"].as<std::string>()).string()};

    if (!fs::exists(target_filename))
    {
        std::cerr << alexcr4ckpentest::colors::red << "[-] " << alexcr4ckpentest::colors::none << "Error: could not open file: " << target_filename << "\n";
        return 1;
    }

    if (json_obj["filename"] != target_filename || (!json_obj["encrypted"]))
    {
        std::cerr << alexcr4ckpentest::colors::red << "[-] " << alexcr4ckpentest::colors::none << "Error: different files encrypted or this file already decrypted!\n";
        return 1;
    }

    const uint64_t base_key{json_obj["base_key"]};
    const uint32_t salt_key{json_obj["salt_key"]};

    file_encryptor_decryptor.set_target_filename(target_filename);
    file_encryptor_decryptor.decrypt(base_key, salt_key);

    json_obj["encrypted"] = false;
    json_file_writer.write(json_obj);

    std::cout << alexcr4ckpentest::colors::green << "[+] " << alexcr4ckpentest::colors::none << "Success: file has been decrypted: " << target_filename << "\n";
  }

  return 0;
}
